#include "GLee.h"

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>

int window;

int windowWidth;
int windowHeight;

typedef struct
{
	float x,y,z;
}Punto;

typedef struct
{
	float r,g,b;
}Color;

typedef struct 
{
	Punto p;
	Color c;
}Vertice;

GLint modelPosUniform;
GLint modelRotUniform;
GLint projectionMatrixUniform;

GLuint modelBufferObject;

GLuint loadShaderFile(GLint shadertype, const char * filename)
{
	int f = open( filename, O_RDONLY );     // abrimos el archivo
	off_t fileLength = lseek(f,0,SEEK_END); // tomamos su tamaño
	lseek(f,0,SEEK_SET);                    // volvemos al principio del archivo
	char * fileText = (char*) malloc(sizeof(char)*(fileLength+1)); // creamos un buffer para almacenar el texto
	read(f, fileText, fileLength);          // leemos el texto
	fileText[fileLength] = 0;
	close( f);

	char * ft = fileText;
	GLuint shader = glCreateShader(shadertype); //GL_FRAGMENT_SHADER o GL_VERTEX_SHADER
	glShaderSource(shader,1,(const GLchar **)&ft,NULL);

	// Lo compilamos 
	glCompileShader(shader);

	GLint shaderCompiledOK;
	glGetShaderiv(shader,GL_COMPILE_STATUS,&shaderCompiledOK);
	if(!shaderCompiledOK)
	{
		int logSize;
		glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&logSize);
		char * log = (char*)malloc(sizeof(char)*logSize+1);
		glGetShaderInfoLog(shader, logSize, &logSize, log);
		printf("Log de compilado: %s\n",log);
		free(log);
	}
	return shader;
}


void InitGL (int windowWidth, int windowHeight, int windowDepth, const char * windowTitle)
{
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);
	glutInitWindowSize(windowWidth,windowHeight);
	window = glutCreateWindow(windowTitle);
	glEnable(GL_DEPTH_TEST);

	// Cargamos los 2 shaders
	GLuint vshader = loadShaderFile(GL_VERTEX_SHADER,"shader.vert");
	GLuint fshader = loadShaderFile(GL_FRAGMENT_SHADER,"shader.frag");

	// Creamos el shader program y unimos los shaders
	GLuint program = glCreateProgram();
	glAttachShader(program, vshader);
	glAttachShader(program, fshader);

	// Enlazamos el programa
	glLinkProgram(program);

	// Indicamos que queremos usar este shader
	glUseProgram(program);
	modelPosUniform = glGetUniformLocation(	program, "modelpos");
	modelRotUniform = glGetUniformLocation(	program, "modelrot");
	projectionMatrixUniform = glGetUniformLocation( program, "projectionMatrix");

	// Creamos un buffer para los vertices del modelo
	glGenBuffers( 1, &modelBufferObject);
	glBindBuffer( GL_ARRAY_BUFFER, modelBufferObject);

	// Coordenadas y colores de los vertices del objeto
	const Vertice v1={{-0.25,-0.15,0.0}, {0.0,0.0,0.9}};
	const Vertice v2={{ 0.25,-0.15,0.0}, {0.0,1.0,0.0}};
	const Vertice v3={{ 0.0,0.35,0.0},   {1.0,0.0,0.0}};
	const Vertice v4={{ 0.0,0.0,-0.35},    {0.0,0.0,0.0}};
	const Vertice vertices[12] = {v1,v2,v3, // cara 1
								v4,v1,v2,	// cara 2
								v3,v4,v1,	// cara 3
								v2,v3,v4};	// cara 4

	glBufferData( GL_ARRAY_BUFFER, sizeof(Vertice)*12,vertices, GL_STATIC_DRAW ); 
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
	const int ESCAPE = 27;

	if (key == ESCAPE) 
	{ 
		glutDestroyWindow(window); 
	    exit(1);
    }
}

void fillProjectionMatrix(float * mat)
{
	float n = 0.01;			// near plane
	float f = 100.0;		// far plane
	float w = windowWidth;			// frustum width
	float h = windowHeight;			// frustum height
	float ri = h/w;
	for(int i=0;i<16;++i)
		mat[i] = 0;
	mat[0] = atan2(w,h)*ri;
	mat[5] = atan2(w,h);
	mat[10] = f/(f-n);
	mat[11] = 1.0;
	mat[14] = -f*n/(f-n);
}

void DrawGLScene()
{	
	static float time = 0;
	time +=0.001;
	// Limpiamos la imagen con color blanco
	glClearColor(1.0,1.0,1.0,1.0);
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	// Activamos el render de vertex y color arrays
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	// Indicamos de qué arrays se deben tomar los datos
	glColorPointer(3,GL_FLOAT,sizeof(Vertice),(void*)sizeof(Punto));
	glVertexPointer(3,GL_FLOAT,sizeof(Vertice),0);

	// Posicion de los diferentes objetos
	const Punto modelspos[5] = { {0,0,0}, {0.4,0.4,0.4}, {-0.4,-0.4,0.4}, {0.4,-0.4,0.4}, {-0.4,0.4,0.4} };

	float projectionMatrix[16];
	fillProjectionMatrix(projectionMatrix);
	glUniformMatrix4fv(projectionMatrixUniform,1,GL_FALSE,projectionMatrix);
	
	glBindBuffer( GL_ARRAY_BUFFER, modelBufferObject);
	for(int i=0;i<5;++i)
	{
		// Enviamos la posicion del objeto a la GPU
		glUniform3fv(modelPosUniform,1,&(modelspos[i].x));
		// Enviamos la rotacion del objeto
		glUniform1f(modelRotUniform,time*(i+1)*0.5);
		// Dibujamos el objeto 
		glDrawArrays(GL_TRIANGLES,0, 12);
	}
	// Mostramos el triangulo en pantalla 
	glutSwapBuffers();
}

void ReSizeGLScene(int width, int height)
{
	if(width<1) width = 1;
	if(height<1) height =1;
	windowWidth = width;
	windowHeight = height;
	glViewport(0, 0, width, height);		
}

int main(int argc, char ** argv) 
{
	glutInit(&argc, argv);  
	windowWidth = 800;
	windowHeight = 600;
	InitGL(windowWidth,windowHeight,24,"OpenGL Tutorial1");
	glutDisplayFunc(&DrawGLScene);  

	glutIdleFunc(&DrawGLScene);
	glutReshapeFunc(&ReSizeGLScene);
	glutKeyboardFunc(&keyPressed);

	glutMainLoop();  
	return 1;
}

